package maskedpixel
{
	//import flash.geom.Vector2D;
	import flash.geom.Point;
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.Graphic;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.graphics.Tilemap;
	import net.flashpunk.Mask;
	import net.flashpunk.masks.Grid;
	
	/**
	 * ...
	 * @author Erin M Gunn
	 */
	public class MpEntity extends Entity 
	{
		/**
		 * Direction flags.  Used mostly with collision.
		 */
		public static const UP:uint = 0x0001;
		public static const DOWN:uint = 0x0010;
		public static const LEFT:uint = 0x0100;
		public static const RIGHT:uint = 0x1000;
		public static const NONE:uint = 0;
		public static const CEILING:uint = UP;
		public static const FLOOR:uint = DOWN;
		public static const WALL:uint = LEFT | RIGHT;
		public static const ANY:uint = UP | DOWN | RIGHT | LEFT;
		
		/**
		 * The velocity of this MpEntity.  Used internally to update the objects position.
		 */
		public var velocity:Vector2D;
		
		/**
		 * The acceleration of this MpEntity.  Used internally to update the objects position.
		 */
		public var acceleration:Vector2D;
		
		/**
		 * The drag of this MpEntity.  Used internally to update the objects position.
		 */
		public var drag:Vector2D;
		
		/**
		 * The maximum velocity of this MpEntity.  Used internally to update the objects position.
		 */
		public var maxVelocity:Vector2D;
		
		/**
		 * The change in position after updateMotion has been called.
		 */
		protected var _deltaPosition:Vector2D;
		
		/**
		 * The angle of this MpEntity in Degrees.  Used by updateMotion.
		 */
		public function get angle():Number
		{
			return _angle;
		}
		public function set angle(value:Number):void
		{
			_angle = value;
			while (_angle > 180) _angle -= 360;
			while (_angle < -180) _angle += 360;
		}
		
		/**
		 * The angular velocity of this MpEntity.  Used by updateMotion.
		 */
		public var angularVel:Number;
		
		/**
		 * The angular acceleration of this MpEntity.  Used by updateMotion.
		 */
		public var angularAccel:Number;
		
		/**
		 * The angular drag of this MpEntity.  Used by updateMotion.
		 */
		public var angularDrag:Number;
		
		/**
		 * The maximum angular velocity of this MpEntity.  Used by updateMotion.
		 */
		public var angularMaxVel:Number;
		
		/**
		 * The change in the angle after updateMotion has been called.
		 */
		protected var _deltaAngle:Number;
		
		/**
		 * A vector that can be used for anything it is needed without having to instantiate a new one.
		 */
		protected var _utilVec:Vector2D;
		
		/**
		 * Used to keep track of which directions the MpEntity is in collision with active Grids.
		 */
		private var _touching:uint;
		
		/**
		 * Used to keep track of which directions this MpEntity was previously in collision with
		 * active Grids.
		 */
		private var _wasTouching:uint;
		
		/**
		 * The center of this Entity.
		 */
		private var _origin:Vector2D;
		
		public function get origin():Vector2D
		{
			return _origin;
		}
		
		/**
		 * @inheritDoc
		 */
		public function MpEntity(x:Number = 0, y:Number = 0, graphic:Graphic = null, mask:Mask = null) 
		{
			super(x, y, graphic, mask);
			
			// Motion
			velocity = new Vector2D();
			acceleration = new Vector2D();
			drag = new Vector2D();
			maxVelocity = new Vector2D(10000, 10000);
			_deltaPosition = new Vector2D();
			_origin = new Vector2D();
			
			// Rotation
			angularVel = 0;
			angularAccel = 0;
			angularDrag = 0;
			angularMaxVel = 10000;
			_deltaAngle = 0;
			
			// Collision
			_touching = NONE;
			_wasTouching = NONE;
			
			// utility
			_utilVec = new Vector2D();
		}
		
		/**
		 * Executed every game loop.
		 * Calls updateMotion() if manageMotion is true.
		 * Calls updateCollisionWithGrids() if manageCollisionswithGrids is true.
		 */
		override public function update():void 
		{
		}
		
		/**
		 * Stolen from Flixel to update the speed and position of this Entity, ensuring round numbers for
		 * x, y and _deltaposision.
		 */
		protected function updateMotion():void
		{
			var maxVel:Number = maxVelocity.x;
			var delta:Vector2D = new Vector2D;
			var velocityDelta:Vector2D = new Vector2D;
			
			velocityDelta.x = (MpPhysics.computeVelocity(velocity.x, acceleration.x, drag.x, maxVel) - velocity.x) / 2;
			velocity.x += velocityDelta.x;
			maxVel = maxVelocity.y == -1 ? maxVelocity.x : maxVelocity.y;
			velocityDelta.y = (MpPhysics.computeVelocity(velocity.y, acceleration.y, drag.y, maxVel) - velocity.y) / 2;
			velocity.y += velocityDelta.y;
			
			delta.x = velocity.x * FP.elapsed;
			delta.y = velocity.y * FP.elapsed;
			velocity.x += velocityDelta.x;
			velocity.y += velocityDelta.y;
			_deltaPosition.x = delta.x;
			_deltaPosition.y = delta.y;
			_move.x += _deltaPosition.x;
			_move.y += _deltaPosition.y;
			_deltaPosition.x = Math.round(_move.x);
			_deltaPosition.y = Math.round(_move.y);
			_move.x -= _deltaPosition.x;
			_move.y -= _deltaPosition.y;
			x += _deltaPosition.x;
			y += _deltaPosition.y;
			
			if (maxVelocity.y == -1 && velocity.magnitude > maxVelocity.x)
			{
				velocity.magnitude = maxVelocity.x;
			}
			
			velocityDelta.y = (MpPhysics.computeVelocity(angularVel, angularAccel, angularDrag, angularMaxVel) - angularVel) / 2;
			angularVel += velocityDelta.y;
			delta.y = angularVel * FP.elapsed;
			angularVel += velocityDelta.y;
			angle += delta.y;
			_deltaAngle = delta.y;
		}
		
		/**
		 * Useful function that handles collisions with Grids and updates which directions
		 * this MpEntity is in contact with the Grids.
		 * NOTE: _deltaPosition MUST HAVE THE AMOUNT THIS MPENTITY'S X AND Y VALUES HAVE CHANGED.
		 * THIS FUNCTION ASSUMES THE MPENTITY HAS ALREADY MOVED.
		 * 
		 * TODO: Make it possible to select an axis to always "doFirst".
		 */
		protected function updateCollisionWithSolids(levelType:String):void
		{
			_wasTouching = _touching;
			_touching = NONE;
			
			
			var e:Entity = collide(levelType, x, y);
			if (!e)
			{
				_touching |= collide(levelType, x + 1, y) != null ? RIGHT : NONE;
				_touching |= collide(levelType, x - 1, y) != null ? LEFT : NONE;
				_touching |= collide(levelType, x, y + 1) != null ? DOWN : NONE;
				_touching |= collide(levelType, x, y - 1) != null ? UP : NONE;
				if (isTouching(RIGHT) && velocity.x > 0)	velocity.x = 0;
				if (isTouching(LEFT) && velocity.x < 0)	velocity.x = 0;
				if (isTouching(UP) && velocity.y < 0) velocity.y = 0;
				if (isTouching(DOWN) && velocity.y > 0) velocity.y = 0;
				return;
			}
			
			x -= _deltaPosition.x;
			y -= _deltaPosition.y;
			
			y += _deltaPosition.y;
			separateY(e);
			x += _deltaPosition.x;
			separateX(e);
			
			_touching |= collide(levelType, x + 1, y) != null ? RIGHT : NONE;
			_touching |= collide(levelType, x - 1, y) != null ? LEFT : NONE;
			_touching |= collide(levelType, x, y + 1) != null ? DOWN : NONE;
			_touching |= collide(levelType, x, y - 1) != null ? UP : NONE;
			if (isTouching(RIGHT) && velocity.x > 0)	velocity.x = 0;
			if (isTouching(LEFT) && velocity.x < 0)	velocity.x = 0;
			if (isTouching(UP) && velocity.y < 0) velocity.y = 0;
			if (isTouching(DOWN) && velocity.y > 0) velocity.y = 0;
		}
		
		/**
		 * Seperates this MpEntity along the x axis from the specified entity.
		 * NOTE: _deltaPosition.x MUST HAVE THE AMOUNT THIS MPENTITY'S X VALUE CHANGED THIS FRAME.
		 * THIS FUNCTION ASSUMES THE MPENTITY HAS ALREADY MOVED.
		 * @param	e			The entity to seperate from.
		 * @param	direction	The direction along the x axis (LEFT or RIGHT) to move this MpEntity out of collision.
		 * 						leaving this as 0 assumes to move this MpEntity in the opposite direction of _deltaPosition.x.
		 */
		protected function separateX(e:Entity, direction:uint = 0):void
		{
			if (!collideWith(e, x, y)) return;
			if (FP.sign(_deltaPosition.x) > 0)
			{
				x -= _deltaPosition.x - Math.floor(_deltaPosition.x);
				_deltaPosition.x -= _deltaPosition.x - Math.floor(_deltaPosition.x);
				while (collideWith(e, x, y))
				{
					x--;
					_deltaPosition.x--;
				}
			}
			else
			{
				x += _deltaPosition.x + Math.ceil(_deltaPosition.x);
				_deltaPosition.x += _deltaPosition.x + Math.ceil(_deltaPosition.x);
				while (collideWith(e, x, y))
				{
					x++;
					_deltaPosition.x++;
				}
			}
			velocity.x = 0;
		}
		
		/**
		 * Seperates this MpEntity along the y axis from the specified entity.
		 * NOTE: _deltaPosition.x MUST HAVE THE AMOUNT THIS MPENTITY'S X VALUE CHANGED THIS FRAME.
		 * THIS FUNCTION ASSUMES THE MPENTITY HAS ALREADY MOVED.
		 * @param	e			The entity to seperate from.
		 * @param	direction	The direction along the x axis (UP or DOWN) to move this MpEntity out of collision.
		 * 						leaving this as 0 assumes to move this MpEntity in the opposite direction of _deltaPosition.y.
		 */
		protected function separateY(e:Entity, direction:uint = 0):void
		{
			if (!collideWith(e, x, y)) return;
			if (FP.sign(_deltaPosition.y) > 0)
			{
				y  -= _deltaPosition.y - Math.floor(_deltaPosition.y);
				_deltaPosition.y  -= _deltaPosition.y - Math.floor(_deltaPosition.y);
				while (collideWith(e, x, y))
				{
					y--;
					_deltaPosition.y--;
				}
			}
			else
			{
				y += _deltaPosition.y + Math.ceil(_deltaPosition.y);
				_deltaPosition.y += _deltaPosition.y + Math.ceil(_deltaPosition.y);
				while (collideWith(e, x, y))
				{
					y++;
					_deltaPosition.y++;
				}
			}
			velocity.y = 0;
		}
		
		/**
		 * Used to find out of this MpEntity is touching the collision grid in the specified direction(s).
		 * @param	direction	Any of the direction constants(UP, RIGHT, FLOOR, ANY, etc).  Can use multiple with the bitwise OR operator:<br>justTouched(UP | DOWN);
		 * @return					True if all of the directions passed are touching.
		 */
		public function isTouching(direction:uint):Boolean
		{
			return (_touching & direction) > NONE;
		}
		
		
		/**
		 * Used to find out if this MpEntity is just touched the collision grid in the specified direction(s).
		 * @param	direction		Any of the direction constants(UP, RIGHT, FLOOR, ANY, etc).  Can use multiple with the bitwise OR operator:<br>justTouched(UP | DOWN);
		 * @return					True if all of the directions passed were just touched.
		 */
		public function justTouched(direction:uint):Boolean
		{
			return ((_touching & direction) > NONE) && ((_wasTouching & direction) <= NONE);
		}
		
		private var _angle:Number = 0;
		private var _move:Point = new Point();
	}

}